﻿#region copyright
//<copyright>
// Copyright(C) 2012 TrackerRealm Corporation
// This file is part of the open source project - Jazz. http://jazz.codeplex.com
// 
// Jazz is open software: you can redistribute it and/or modify it 
// under the terms of the GNU Affero General Public License (AGPL) as published by 
// the Free Software Foundation, version 3 of the License.
// 
// Jazz is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty 
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
//  See the GNU Affero General Public License (AGPL) for more details.
// 
// You should have received a copy of the GNU General Public 
// License along with Jazz.  If not, see <http://www.gnu.org/licenses/>.
//
// REMOVAL OF THIS NOTICE IS VIOLATION OF THE COPYRIGHT. 
//</copyright>
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;

namespace TrackerRealm.Jazz.Client
{
    /// <summary>
    /// 
    /// </summary>
    public static class ReflectionExtensionMethods
    {
        //
        // IEnumerable<MemberInfo> Extension methods
        //
        #region UI
        /// <summary>
        /// Returns 'MemberInfo' elements from a sequence that are marked by attributes as 'UI'.
        /// <para>This method is useful in only returning members that are involved in the application interface. </para>
        /// </summary>
        /// <param name="members"></param>
        /// <returns></returns>
        public static IEnumerable<MemberInfo> UI(this IEnumerable<MemberInfo> members)
        {
            foreach (MemberInfo member in members)
            {
                if (member.GetCustomAttributes(typeof(UIAttribute), true).Length >= 1)
                {
                    yield return member;
                    if (member.MemberType == MemberTypes.Property)
                    {
                        // Also get the setter and getter methods if they exist.
                        MemberInfo[] mA = member.DeclaringType.GetMember("set_" + member.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                        if (mA != null && mA.Length > 0)
                            yield return mA[0];
                        mA = member.DeclaringType.GetMember("get_" + member.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                        if (mA != null && mA.Length > 0)
                            yield return mA[0];
                    }
                    continue;
                }
            }
        }
        /// <summary>
        /// Returns 'jMemberInfo' elements from a sequence that are marked by attributes as 'UI'.
        /// <para>This method is useful in only returning members that are involved in the application interface. </para>
        /// </summary>
        /// <param name="members"></param>
        /// <returns></returns>
        public static IEnumerable<jMemberInfo> UI(this IEnumerable<jMemberInfo> members)
        {
            foreach (jMemberInfo member in members)
            {
                if (member.Member.GetCustomAttributes(typeof(UIAttribute), true).Length >= 1)
                {
                    yield return member;
                }
            }
        }
        #endregion
        #region GetAccessorProperty
        /// <summary>
        /// Returns the property of the accessor method.
        /// </summary>
        /// <param name="method">This method must be an accessor.</param>
        /// <returns></returns>
        internal static PropertyInfo GetAccessorProperty(this MethodInfo method)
        {
            
            Type t = method.ReflectedType;
            while (t.Name != "jObject")
            {
                MemberInfo[] members = t.GetMember(method.Name.Remove(0, 4), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).ToArray();
                foreach (MemberInfo m in members)
                {
                    if (m is PropertyInfo)
                    {
                        PropertyInfo p = (PropertyInfo)m;
                        MethodInfo[] mArray = p.GetAccessors(true);
                        if (p.GetAccessors(true).Contains(method)) return p;
                    }
                }
                t = t.BaseType;
            }
            throw new InvalidProgramException();
        }
        #endregion
        #region GrantedAccess
        /// <summary>
        /// Cache attribute information.
        /// </summary>
        private static Dictionary <MemberInfo, Tuple<GrantStatesAttribute, GrantRolesAttribute, GrantPropertyAttribute>> grantDict = 
            new Dictionary<MemberInfo,Tuple<GrantStatesAttribute,GrantRolesAttribute,GrantPropertyAttribute>>();
        /// <summary>
        /// 
        /// </summary>
        /// <param name="member"></param>
        /// <param name="flow"></param>
        /// <returns></returns>
        internal static bool IsGrantedAccess(this MemberInfo member, jObject flow)
        {
            Tuple<GrantStatesAttribute, GrantRolesAttribute, GrantPropertyAttribute> tup;
            if(!(grantDict.TryGetValue(member, out tup)))
            {
                object[] stateMethods = member.GetCustomAttributes(typeof(GrantStatesAttribute), true);
                object[] roleMethods = member.GetCustomAttributes(typeof(GrantRolesAttribute), true);
                object[] propertyAttributes = member.GetCustomAttributes(typeof(GrantPropertyAttribute), true);
                if (stateMethods.Length == 0 && roleMethods.Length == 0 && propertyAttributes.Length == 0 &&(member.Name.StartsWith("set_") || member.Name.StartsWith("get_")))
                {
                    MemberInfo prop = ((MethodInfo)member).GetAccessorProperty();
                    stateMethods = prop.GetCustomAttributes(typeof(GrantStatesAttribute), true);
                    roleMethods = prop.GetCustomAttributes(typeof(GrantRolesAttribute), true);
                    propertyAttributes = prop.GetCustomAttributes(typeof(GrantPropertyAttribute), true);
                }
                GrantStatesAttribute gsa = null;
                GrantRolesAttribute gra = null;
                GrantPropertyAttribute gpa = null;
                if (stateMethods.Length >= 1 || roleMethods.Length >= 1 || propertyAttributes.Length >= 1)
                {
                    if (stateMethods.Length >= 1) // don't worry if there is more than 1 attribute - error checked during jClass.CrossCheck() on initial nexus.Bind()
                    {
                        gsa = (GrantStatesAttribute)stateMethods[0];
                    }
                    if (roleMethods.Length >= 1) // don't worry if there is more than 1 attribute - error checked during jClass.CrossCheck() on initial nexus.Bind()
                    {
                        gra = (GrantRolesAttribute)roleMethods[0];
                    }
                    if (propertyAttributes.Length >= 1) // don't worry if there is more than 1 attribute - error checked during jClass.CrossCheck() on initial nexus.Bind()
                    {
                        gpa = (GrantPropertyAttribute)propertyAttributes[0];
                    }
                }
                tup = new Tuple<GrantStatesAttribute,GrantRolesAttribute,GrantPropertyAttribute>(gsa,gra, gpa);
                grantDict.Add(member, tup);
            }
            if(tup.Item1 == null && tup.Item2 == null && tup.Item3 == null) return true;
            if(tup.Item1 != null)
            {
                if (!JazzAttribute.IsAccessible_States(flow, tup.Item1.States)) return false;
            }
            if(tup.Item2 != null)
            {
                if (!JazzAttribute.IsAccessible_Roles(flow, tup.Item2.Roles)) return false;
            }
            if (tup.Item3 != null)
            {
                MethodInfo m = flow.Class.GetMethodInfo("get_" + tup.Item3.Property);
                object prop = m.Invoke(flow, new object[0]);
                if (prop is jProfileBase)
                {
                    if (((jProfileBase)prop)._Profile != flow.ClientNexus._LoginProfile)
                        return false;
                    return true;
                }
                else if (prop is IEnumerable<jProfileBase>)
                {
                    bool isValidUser = false;
                    foreach (jProfileBase user in (IEnumerable<jProfileBase>)prop)
                    {
                        if (user == flow.ClientNexus._LoginProfile)
                        {
                            isValidUser = true;
                            break;
                        }
                    }
                    if (!isValidUser)
                    {
                        return false;
                    }
                    return true;
                }
                else if (prop is bool)
                {
                    if (!(bool)prop) return false;
                    return true;
                }
                else if (prop is IEnumerable<jRoleBase>)
                {
                    if (!JazzAttribute.IsAccessible_Roles(flow, ((IEnumerable<jRoleBase>)prop).Select<jRoleBase, string>(r => r.Name)))
                    {
                        return false;
                    }
                    return true;
                }
                else if (prop is IEnumerable<jState>)
                {
                    if (!JazzAttribute.IsAccessible_States(flow, ((IEnumerable<jState>)prop).Select<jState, string>(r => r.Name)))
                    {
                        string[] roles = ((IEnumerable<jRoleBase>)prop).Select<jRoleBase, string>(r => r.Name).ToArray();
                        return false;
                    }
                }
                else throw new InvalidOperationException(string.Format("The property '{0}' returned an unexpected type, '{1}'.", tup.Item3.Property, prop.GetType().FullName));
            }
            return true;
            
        }
        /// <summary>
        /// Returns 'MemberInfo' elements from a sequence that access has been 'Granted' (is allowed) for the specified '<paramref name="flow"/>'.
        /// <para>Which members are granted access is dynamic.  
        /// and may change as function of the workflow properties or states or as function of the 
        /// 'Login User'.</para>
        /// </summary>
        /// <param name="members"></param>
        /// <param name="flow"></param>
        /// <returns></returns>
        public static IEnumerable<MemberInfo> GrantedAccess(this IEnumerable<MemberInfo> members, jObject flow)
        {
            foreach (MemberInfo member in members)
            {
                if(member.IsGrantedAccess(flow)) yield return member;
            }
        }
        #endregion
        #region ToWrapper
        
        /// <summary>
        /// Returns a '<see cref="jMemberInfo"/>' list of wrapped 'MemberInfo' objects. 
        /// <para>Returns only methods and properties.  Will not return read or write accessors as methods.</para>
        /// <para>The '<see cref="jMemberInfo"/>' properties, such as 'IsPublic', 'CanRead', 'CanWrite' and 'IsGrey',
        /// will take into account 'Grant' values of the <paramref name="jazzObj"/> parameter.</para>
        /// </summary>
        /// <param name="members"></param>
        /// <param name="jazzObj">This may be null in which case properties such as 'IsGrey' have no meaning.</param>
        /// <returns></returns>
        public static IEnumerable<jMemberInfo> ToWrapper(this IEnumerable<MemberInfo> members, jObject jazzObj)
        {
            Dictionary<string, jPropertyInfo> dict = new Dictionary<string, jPropertyInfo>(16);
            MemberInfo[] memberArray = members.ToArray();
            foreach (MemberInfo member in memberArray)
            {
                if (member is PropertyInfo)
                {
                    string name = member.DeclaringType.FullName + "." + member.Name;
                    if (!dict.ContainsKey(name)) 
                    {
                        dict.Add(name, new jPropertyInfo((PropertyInfo)member, jazzObj));
                    }
                    else throw new ArgumentException(string.Format("Can not handle a sequence with duplicate properties names ('{0}').", name));
                }
            }
            foreach (MemberInfo member in memberArray)
            {
                if (member is MethodInfo)
                {
                    MethodInfo m = (MethodInfo)member;
                    if (m.Name.StartsWith("get_"))
                    {
                        string name = m.Name.Remove(0, 4);
                        jPropertyInfo p;
                        if (dict.TryGetValue(m.DeclaringType.FullName + "." + name, out p))
                        {
                            p.ReadAccessor = new jReadAccessor(m, p, jazzObj);
                        }
                    }
                    else if (m.Name.StartsWith("set_"))
                    {
                        string name = m.Name.Remove(0, 4);
                        jPropertyInfo p;
                        if (dict.TryGetValue(m.DeclaringType.FullName + "." + name, out p))
                        {
                            p.WriteAccessor = new jWriteAccessor(m, p, jazzObj);
                        }
                    }
                    else yield return new jMethodInfo(m, jazzObj);
                }
            }
            // needs to search for missing read and write accessors
            foreach (jPropertyInfo p in dict.Values)
            {
                string name = p.Name.Split('.').Last();
                if (p.WriteAccessor.IsEmpty)
                {
                    MethodInfo method = p.PropertyInfo.GetAccessors().FirstOrDefault(mthd => mthd.Name.StartsWith("set_"));
                    if(method != null)
                        p.WriteAccessor = new jWriteAccessor(method, p, jazzObj);
                }
                if (p.ReadAccessor.IsEmpty)
                {
                    MethodInfo method = p.PropertyInfo.GetAccessors().FirstOrDefault(mthd => mthd.Name.StartsWith("get_"));
                    if (method != null)
                        p.ReadAccessor = new jReadAccessor(method, p, jazzObj);
                }
                
            }
            foreach(jPropertyInfo p in dict.Values)
            {
                yield return p;
            }
        }
        #endregion
        #region Properties
        /// <summary>
        /// 
        /// </summary>
        /// <param name="members"></param>
        /// <returns></returns>
        public static IEnumerable<jPropertyInfo> Properties(this IEnumerable<jMemberInfo> members)
        {
            foreach (jMemberInfo member in members)
            {
                if (member.IsProperty) yield return (jPropertyInfo)member;
            }
        }
        #endregion
        #region Methods
        /// <summary>
        /// 
        /// </summary>
        /// <param name="members"></param>
        /// <returns></returns>
        public static IEnumerable<jMethodInfo> Methods(this IEnumerable<jMemberInfo> members)
        {
            foreach (jMemberInfo member in members)
            {
                if (member.IsMethod) yield return (jMethodInfo)member;
            }
        }
        #endregion
        #region IsSerializeIgnore
        /// <summary>
        /// Returns true if the member has the attribute 'SerializeIgnore'.
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        public static bool IsSerializeIgnoreAttribute(this MemberInfo member)
        {
            return (member.GetCustomAttributes(typeof(SerializeIgnoreAttribute), true).FirstOrDefault() != null);
        }
        #endregion

        #region IsBaseField
        /// <summary>
        /// Returns true if the member has the attribute 'BaseField'.
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        public static bool IsBaseFieldAttribute(this MemberInfo member)
        {
            return (member.GetCustomAttributes(typeof(BaseFieldAttribute), true).FirstOrDefault() != null);
        }
        #endregion
        #region IsAccessorMethodAttribute
        /// <summary>
        /// Returns true if the member has the attribute 'AccessorMethod'.
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        public static bool IsAccessorMethodAttribute(this MethodBase member)
        {
            return (member.GetCustomAttributes(typeof(AccessorMethodAttribute), true).FirstOrDefault() != null);
        }
        #endregion

        #region IsDoNotAutoInitializeAttribute
        /// <summary>
        /// Returns true if the member has the attribute 'DoNotAutoInitialize'.
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        public static bool IsDoNotAutoInitializeAttribute(this MemberInfo member)
        {
            return (member.GetCustomAttributes(typeof(DoNotAutoInitializeAttribute), true).FirstOrDefault() != null);
        }
        #endregion

        #region IsEmptySingletonAttribute
        /// <summary>
        /// Returns true if the member has the attribute 'EmptySingleton'.
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        public static bool IsEmptySingletonAttribute(this MemberInfo member)
        {
            return (member.GetCustomAttributes(typeof(EmptySingletonAttribute), true).FirstOrDefault() != null);
        }
        #endregion
        #region TryGetEmptyObjectAttribute
        /// <summary>
        /// Returns true if the member has the attribute 'EmptyObject'.
        /// </summary>
        /// <param name="member"></param>
        /// <param name="className"></param>
        /// <returns></returns>
        public static bool TryGetEmptyObjectAttribute(this MemberInfo member, out string className)
        {
            EmptyObjectAttribute attr = (EmptyObjectAttribute)member.GetCustomAttributes(typeof(EmptyObjectAttribute), false).FirstOrDefault();
            className = "";
            if (attr == null) return false;
            className = attr.ClassName;
            return true;
        }
        #endregion
        #region TryGet_UseEmptyObjectForAttribute
        /// <summary>
        /// Returns true if the member has the attribute 'UseEmptyObjectForAttribute'.
        /// </summary>
        /// <param name="member"></param>
        /// <param name="classTypes"></param>
        /// <returns></returns>
        public static bool TryGet_UseEmptyObjectForAttribute(this MemberInfo member, out Type[] classTypes)
        {
            UseEmptyObjectForAttribute attr = (UseEmptyObjectForAttribute)member.GetCustomAttributes(typeof(UseEmptyObjectForAttribute), false).FirstOrDefault();
            classTypes = new Type[0];
            if (attr == null) return false;
            classTypes = attr.ClassTypes;
            return true;
        }
        #endregion
    }
}
